package com.nexgo.payment.trans.setting.terminalkey.iccimportkey;

import android.content.Context;
import android.support.annotation.NonNull;
import android.text.TextUtils;

import com.nexgo.common.ByteUtils;
import com.nexgo.common.Des;
import com.nexgo.oaf.apiv3.SdkResult;
import com.nexgo.oaf.apiv3.card.cpu.APDUEntity;
import com.nexgo.oaf.apiv3.card.cpu.CPUCardHandler;
import com.nexgo.oaf.apiv3.device.pinpad.AlgorithmModeEnum;
import com.nexgo.oaf.apiv3.device.pinpad.PinPad;
import com.nexgo.oaf.apiv3.device.reader.CardInfoEntity;
import com.nexgo.oaf.apiv3.device.reader.CardReader;
import com.nexgo.oaf.apiv3.device.reader.CardSlotTypeEnum;
import com.nexgo.oaf.apiv3.device.reader.OnCardInfoListener;
import com.nexgo.payment.R;
import com.nexgo.payment.aop.annotation.RunOnMainThread;
import com.nexgo.payment.api.ApiDeviceEngine;
import com.nexgo.payment.constrant.PreferencesConstants;
import com.nexgo.payment.constrant.SysConstant;
import com.nexgo.payment.databases.DataSource;
import com.nexgo.payment.util.StringUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * IC卡导密钥
 *
 * @author 谭忠扬-YuriTam
 * @time 2017年08月30日
 */
public class ICCardImportKeyPresenter implements ICCardImportKeyContract.Presenter {
    private Logger mLog = LoggerFactory.getLogger(ICCardImportKeyPresenter.class.getSimpleName());

    private final Context mContext;
    private final ICCardImportKeyContract.View mView;
    private final DataSource mRepository;
    private PinPad mPinPad;
    private CardReader mCardReader;
    private CPUCardHandler mCardHandler;
    private String mKeySn;
    private String mCardPwd;
    private String mStorePwd;
    private byte[] mDecKey = null;
    private byte[] mCkVal = null;
    private int algorithmType;

    public ICCardImportKeyPresenter(@NonNull Context context, @NonNull ICCardImportKeyContract.View view, @NonNull DataSource repository) {
        mContext = context.getApplicationContext();
        mView = checkNotNull(view);
        mRepository = checkNotNull(repository);
        mView.setPresenter(this);
        mPinPad = ApiDeviceEngine.getInstance().getPinPad();
        mCardReader = ApiDeviceEngine.getInstance().getCardReader();
        mCardHandler = ApiDeviceEngine.getInstance().getCPUCardHandler(CardSlotTypeEnum.ICC1);
        algorithmType = Integer.parseInt(StringUtils.firstNonEmpty(mRepository.getParamValue(PreferencesConstants.MASTER_KEY_ALGORITHM), "0"));
        if (algorithmType == AlgorithmType.SM4.getCode()) {
            mPinPad.setAlgorithmMode(AlgorithmModeEnum.SM4);
        }
    }

    @Override
    public void start() {

    }

    @Override
    public void pause() {

    }

    @Override
    public void destroy() {

    }

    @Override
    public void confirm(String keySn, String cardPassword, String storePassword) {
        if (TextUtils.isEmpty(keySn) || TextUtils.isEmpty(cardPassword) || TextUtils.isEmpty(storePassword)) {
            postMainThread(() -> mView.showInputEmpty());
            return;
        }
        if (keySn.length() != 11) {
            postMainThread(() -> mView.showErrMsg(mContext.getString(R.string.pls_input_correct_key_sn)));
            return;
        }
        if (cardPassword.length() < 6 || storePassword.length() < 6) {
            postMainThread(() -> mView.showErrMsg(mContext.getString(R.string.pls_input_6_12_length_password)));
            return;
        }
        if (cardPassword.length() % 2 != 0) {
            cardPassword = cardPassword + "F";
        }
        if (storePassword.length() % 2 != 0) {
            storePassword = storePassword + "F";
        }
        this.mKeySn = keySn;
        this.mCardPwd = cardPassword;
        this.mStorePwd = storePassword;
        postMainThread(() -> mView.showMsg(mContext.getString(R.string.pls_insert_key_card)));
        readCardByAction(1);
    }

    @Override
    public void readCardByAction(int actionStep) {
        //actionStep:｛1 = 读密钥卡｝、｛2 = 读维护卡｝
        try {
            HashSet<CardSlotTypeEnum> slotTypes = new HashSet<>();
            if (actionStep == 1) {
                slotTypes.add(CardSlotTypeEnum.ICC1);
                mCardReader.searchCard(slotTypes, 60, mCardInfoListener_1);
            } else {
                slotTypes.add(CardSlotTypeEnum.ICC2);
                mCardReader.searchCard(slotTypes, 60, mCardInfoListener_2);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void checkCardExist() {
        new Thread(() -> {
            long timeId = System.currentTimeMillis();
            while (true) {
                if (System.currentTimeMillis() > timeId + 60 * 1000) {
                    postMainThread(() -> mView.showMsg(mContext.getString(R.string.operate_time_out)));
                    postMainThread(() -> mView.onDismiss());
                    break;
                }
                if (mCardReader.isCardExist(CardSlotTypeEnum.ICC1)) {
                    postMainThread(() -> mView.showMsg(mContext.getString(R.string.key_card_read_finish)));
                    continue;
                }
                postMainThread(() -> mView.showMsg(mContext.getString(R.string.pls_insert_decode_card)));
                readCardByAction(2);
                break;
            }
        }).start();
    }

    /**
     * 读取密钥卡信息回调
     */
    private OnCardInfoListener mCardInfoListener_1 = new OnCardInfoListener() {

        @Override
        public void onCardInfo(int retCode, CardInfoEntity cardInfo) {
            if (retCode != SdkResult.Success) {
                postMainThread(() -> mView.showErrMsg(mContext.getString(R.string.read_card_fail)));
                postMainThread(() -> mView.onDismiss());
                return;
            }
            postMainThread(() -> mView.showMsg(mContext.getString(R.string.downing_key_and_not_move_card)));
            mDecKey = new byte[16];
            mCkVal = new byte[4];
            int keyNum = 0;
            byte[] TmkSn = new byte[4];
            byte[] atr = new byte[128];
            mCardHandler.powerOn(atr);
            int cpCode = Integer.parseInt(mKeySn.substring(0, 3));
            int batchCode = Integer.parseInt(mKeySn.substring(3, 8));
            int tmkCode = Integer.parseInt(mKeySn.substring(8, 11));
            TmkSn[0] = (byte) cpCode;
            TmkSn[1] = (byte) (batchCode / 256);
            TmkSn[2] = (byte) (batchCode % 256);
            TmkSn[3] = (byte) tmkCode;
            //存储卡 验证密码 0x00,0x20,0x00,0x81
            APDUEntity apduEntity = new APDUEntity();
            apduEntity.setCla((byte) 0x00);
            apduEntity.setIns((byte) 0x20);
            apduEntity.setP1((byte) 0x00);
            apduEntity.setP2((byte) 0x81);
            byte[] arr = new byte[256];
            Arrays.fill(arr, (byte) 0xFF);
            byte[] dat = ByteUtils.hexString2ByteArray(mStorePwd);
            System.arraycopy(dat, 0, arr, 0, dat.length);
            apduEntity.setDataIn(arr);
            apduEntity.setLc(8);
            apduEntity.setLe(0);
            apduEntity.setSwa((byte) 0x00);
            apduEntity.setSwb((byte) 0x00);
            int result = mCardHandler.exchangeAPDUCmd(apduEntity);
            if (result != SdkResult.Success || !isCmdSuc(apduEntity)) {
                postMainThread(() -> mView.showErrMsg(mContext.getString(R.string.password_verify_fail)));
                postMainThread(() -> mView.onDismiss());
                byte a = apduEntity.getSwa();
                byte b = apduEntity.getSwb();
                mLog.debug("return:{},{}", a, b);
                return;
            }
            //选择存放密钥的二进制文件：0x00, 0xA4, 0x00, 0x0C, 0x02, 0xEF, 0x09
            apduEntity = new APDUEntity();
            apduEntity.setCla((byte) 0x00);
            apduEntity.setIns((byte) 0xA4);
            apduEntity.setP1((byte) 0x00);
            apduEntity.setP2((byte) 0x0C);
            arr = new byte[256];
            Arrays.fill(arr, (byte) 0xFF);
            dat = ByteUtils.hexString2ByteArray("EF09");
            System.arraycopy(dat, 0, arr, 0, dat.length);
            apduEntity.setDataIn(arr);
            apduEntity.setLc(2);
            apduEntity.setLe(0);
            apduEntity.setSwa((byte) 0x00);
            apduEntity.setSwb((byte) 0x00);
            result = mCardHandler.exchangeAPDUCmd(apduEntity);
            if (result != SdkResult.Success || !isCmdSuc(apduEntity)) {
                postMainThread(() -> mView.showErrMsg(mContext.getString(R.string.select_file_fail)));
                postMainThread(() -> mView.onDismiss());
                byte a = apduEntity.getSwa();
                byte b = apduEntity.getSwb();
                mLog.debug("return:{},{}", a, b);
                return;
            }
            //读出密钥条数：0x00, 0xB0, 0x00, 0x00, 0x03
            apduEntity = new APDUEntity();
            apduEntity.setCla((byte) 0x00);
            apduEntity.setIns((byte) 0xB0);
            apduEntity.setP1((byte) 0x00);
            apduEntity.setP2((byte) 0x00);
            arr = new byte[256];
            apduEntity.setDataIn(arr);
            apduEntity.setLc(0);
            apduEntity.setLe(3);
            apduEntity.setSwa((byte) 0x00);
            apduEntity.setSwb((byte) 0x00);
            result = mCardHandler.exchangeAPDUCmd(apduEntity);
            if (result != SdkResult.Success || !isCmdSuc(apduEntity)) {
                postMainThread(() -> mView.showErrMsg(mContext.getString(R.string.read_file_fail)));
                postMainThread(() -> mView.onDismiss());
                byte a = apduEntity.getSwa();
                byte b = apduEntity.getSwb();
                mLog.debug("return:{},{}", a, b);
                return;
            }
            byte[] keyNumBytes = Arrays.copyOfRange(apduEntity.getDataOut(), 0, 3);
            keyNum = Integer.valueOf(new String(keyNumBytes)).intValue();
            //在IC卡中查找前4字节为序号的密钥：每次读取24字节（0x18），
            //0x00, 0xB0, P1, P2, 0x18
            //P1,P2为偏移值，第一条时P1应为0x00,P2应为0x03。第二条时，P1，P2应分别为0x03+0x18的高底字节。以此类推。一直读到序号相同的24字节。
            int keyIndex = 0;
            for (keyIndex = 0; keyIndex < keyNum; keyIndex++) {
                byte pLen = (byte) (keyIndex * 24 + 3);
                byte p1 = (byte) (pLen / 256);
                byte p2 = (byte) (pLen % 256);
                byte lpLe = 24;

                apduEntity = new APDUEntity();
                apduEntity.setCla((byte) 0x00);
                apduEntity.setIns((byte) 0xB0);
                apduEntity.setP1(p1);
                apduEntity.setP2(p2);
                arr = new byte[256];
                apduEntity.setDataIn(arr);
                apduEntity.setLc(0);
                apduEntity.setLe(lpLe);
                apduEntity.setSwa((byte) 0x00);
                apduEntity.setSwb((byte) 0x00);
                result = mCardHandler.exchangeAPDUCmd(apduEntity);
                if (result != SdkResult.Success || !isCmdSuc(apduEntity)) {
                    postMainThread(() -> mView.showErrMsg(mContext.getString(R.string.read_file_fail)));
                    postMainThread(() -> mView.onDismiss());
                    byte a = apduEntity.getSwa();
                    byte b = apduEntity.getSwb();
                    mLog.debug("return:{},{}", a, b);
                    return;
                }
                if (Arrays.equals(TmkSn, Arrays.copyOfRange(apduEntity.getDataOut(), 0, 4))) {
                    mLog.debug("读到密钥卡数据：{}", ByteUtils.byteArray2HexString(apduEntity.getDataOut()));
                    System.arraycopy(apduEntity.getDataOut(), 4, mDecKey, 0, 16);
                    System.arraycopy(apduEntity.getDataOut(), 20, mCkVal, 0, 4);
                    break;
                }

            }
            if (keyIndex >= keyNum) {
                postMainThread(() -> mView.showErrMsg(mContext.getString(R.string.can_no_find_key)));
                postMainThread(() -> mView.onDismiss());
                return;
            }
            postMainThread(() -> mView.showMsg(mContext.getString(R.string.key_card_read_finish)));
            checkCardExist();
        }

        @Override
        public void onSwipeIncorrect() {

        }

        @Override
        public void onMultipleCards() {

        }
    };

    /**
     * 读取维护卡信息回调
     */
    private OnCardInfoListener mCardInfoListener_2 = new OnCardInfoListener() {

        @Override
        public void onCardInfo(int retCode, CardInfoEntity cardInfo) {
            if (retCode != SdkResult.Success) {
                postMainThread(() -> mView.showErrMsg(mContext.getString(R.string.read_card_fail)));
                postMainThread(() -> mView.onDismiss());
                return;
            }
            postMainThread(() -> mView.showMsg(mContext.getString(R.string.downing_key_and_not_move_card)));
            byte[] atr = new byte[128];
            mCardHandler.powerOn(atr);
            //验证密码 0x00,0x20,0x00,0x81
            APDUEntity apduEntity = new APDUEntity();
            apduEntity.setCla((byte) 0x00);
            apduEntity.setIns((byte) 0x20);
            apduEntity.setP1((byte) 0x00);
            apduEntity.setP2((byte) 0x81);
            byte[] arr = new byte[256];
            Arrays.fill(arr, (byte) 0xFF);
            byte[] dat = ByteUtils.hexString2ByteArray(mCardPwd);
            System.arraycopy(dat, 0, arr, 0, dat.length);
            apduEntity.setDataIn(arr);
            apduEntity.setLc(8);
            apduEntity.setLe(0);
            apduEntity.setSwa((byte) 0x00);
            apduEntity.setSwb((byte) 0x00);
            int result = mCardHandler.exchangeAPDUCmd(apduEntity);
            if (result != SdkResult.Success || !isCmdSuc(apduEntity)) {
                postMainThread(() -> mView.showErrMsg(mContext.getString(R.string.password_verify_fail)));
                postMainThread(() -> mView.onDismiss());
                byte a = apduEntity.getSwa();
                byte b = apduEntity.getSwb();
                mLog.debug("return:{},{}", a, b);
                return;
            }
            //0x00, 0xA4, 0x00, 0x0C, 0x02, 0xEF, 0x08 选择文件
            apduEntity = new APDUEntity();
            apduEntity.setCla((byte) 0x00);
            apduEntity.setIns((byte) 0xA4);
            apduEntity.setP1((byte) 0x00);
            apduEntity.setP2((byte) 0x0C);
            arr = new byte[256];
            Arrays.fill(arr, (byte) 0xFF);
            dat = ByteUtils.hexString2ByteArray("EF08");
            System.arraycopy(dat, 0, arr, 0, dat.length);
            apduEntity.setDataIn(arr);
            apduEntity.setLc(2);
            apduEntity.setLe(0);
            apduEntity.setSwa((byte) 0x00);
            apduEntity.setSwb((byte) 0x00);
            result = mCardHandler.exchangeAPDUCmd(apduEntity);
            if (result != SdkResult.Success || !isCmdSuc(apduEntity)) {
                postMainThread(() -> mView.showErrMsg(mContext.getString(R.string.select_file_fail)));
                postMainThread(() -> mView.onDismiss());
                byte a = apduEntity.getSwa();
                byte b = apduEntity.getSwb();
                mLog.debug("return:{},{}", a, b);
                return;
            }
            //0x00, 0xB0, 0x00, 0x00, 0x08 此指令返回8节有效期YYYYMMDD
            apduEntity = new APDUEntity();
            apduEntity.setCla((byte) 0x00);
            apduEntity.setIns((byte) 0xB0);
            apduEntity.setP1((byte) 0x00);
            apduEntity.setP2((byte) 0x00);
            arr = new byte[256];
            apduEntity.setDataIn(arr);
            apduEntity.setLc(0);
            apduEntity.setLe(8);
            apduEntity.setSwa((byte) 0x00);
            apduEntity.setSwb((byte) 0x00);
            result = mCardHandler.exchangeAPDUCmd(apduEntity);
            if (result != SdkResult.Success || !isCmdSuc(apduEntity)) {
                postMainThread(() -> mView.showErrMsg(mContext.getString(R.string.read_file_fail)));
                postMainThread(() -> mView.onDismiss());
                byte a = apduEntity.getSwa();
                byte b = apduEntity.getSwb();
                mLog.debug("return:{},{}", a, b);
                return;
            }
            if (apduEntity.getDataOut().length < 8) {
                postMainThread(() -> mView.showErrMsg(mContext.getString(R.string.err_date_format)));
                postMainThread(() -> mView.onDismiss());
                return;
            }
            if (!checkExpertDate(new String(Arrays.copyOfRange(apduEntity.getDataOut(), 0, 8)))) {
                postMainThread(() -> mView.showErrMsg(mContext.getString(R.string.key_expdate_invalid)));
                postMainThread(() -> mView.onDismiss());
                return;
            }
            //解密 0x80, 0xF8, 0x01, 0x02, 0x10, _in, 0x10
            apduEntity = new APDUEntity();
            apduEntity.setCla((byte) 0x80);
            apduEntity.setIns((byte) 0xF8);
            apduEntity.setP1((byte) 0x01);
            apduEntity.setP2((byte) 0x02);
            arr = new byte[256];
            Arrays.fill(arr, (byte) 0xFF);
            System.arraycopy(mDecKey, 0, arr, 0, 16);
            apduEntity.setDataIn(arr);
            apduEntity.setLc(16);
            apduEntity.setLe(16);
            apduEntity.setSwa((byte) 0x00);
            apduEntity.setSwb((byte) 0x00);
            result = mCardHandler.exchangeAPDUCmd(apduEntity);
            if (result != SdkResult.Success || !isCmdSuc(apduEntity)) {
                postMainThread(() -> mView.showErrMsg(mContext.getString(R.string.key_decode_fail)));
                postMainThread(() -> mView.onDismiss());
                byte a = apduEntity.getSwa();
                byte b = apduEntity.getSwb();
                mLog.debug("return:{},{}", a, b);
                return;
            }
            //还需要3DES检查校验值
            byte[] temp = new byte[16];
            byte[] key = new byte[16];
            Arrays.fill(temp, (byte) 0x00);
            mLog.debug("密钥：{}", ByteUtils.byteArray2HexString(apduEntity.getDataOut()));
            System.arraycopy(apduEntity.getDataOut(), 0, key, 0, 16);
            byte[] curKvl = Des.trides_crypt(key, temp);
            if (!Arrays.equals(mCkVal, Arrays.copyOfRange(curKvl, 0, 4))) {
                postMainThread(() -> mView.showErrMsg(mContext.getString(R.string.verify_fail)));
                postMainThread(() -> mView.onDismiss());
                return;
            }
            byte[] tmk = null;
            if (algorithmType == AlgorithmType.TDES.getCode()) {
                tmk = new byte[16];
                System.arraycopy(apduEntity.getDataOut(), 0, tmk, 0, 16);
            } else {
                tmk = new byte[8];
                System.arraycopy(apduEntity.getDataOut(), 0, tmk, 0, 8);
            }
            //密钥索引
            int index = Integer.parseInt(StringUtils.firstNonEmpty(mRepository.getParamValue(PreferencesConstants.MASTER_KEY_INDEX), "0"));
            result = mPinPad.writeMKey(index, key, key.length);
            mLog.debug("loadMK result={},index={}", result, index);
            postMainThread(() -> mView.onDismiss());
            if (result != SdkResult.Success) {
                postMainThread(() -> mView.showFail());
                return;
            }
            postMainThread(() -> mView.showSuccessful());
        }

        @Override
        public void onSwipeIncorrect() {

        }

        @Override
        public void onMultipleCards() {

        }
    };

    /**
     * 判断命令
     *
     * @param apduEntity
     * @return
     */
    private boolean isCmdSuc(APDUEntity apduEntity) {
        if (apduEntity.getSwa() == (byte) 0x90 && apduEntity.getSwb() == (byte) 0x00) {
            return true;
        }
        return false;
    }

    /**
     * 检查密钥有效期
     *
     * @param date 有效期
     * @return
     */
    private boolean checkExpertDate(String date) {
        DateFormat df = new SimpleDateFormat("yyyyMMdd");
        String curDate = new SimpleDateFormat("yyyyMMdd", SysConstant.LOCALE).format(new Date());
        try {
            Date dt1 = df.parse(date);
            Date dt2 = df.parse(curDate);
            if (dt1.getTime() > dt2.getTime()) {
                return true;
            }
            return false;
        } catch (Exception exception) {
            exception.printStackTrace();
            return false;
        }
    }

    @RunOnMainThread
    private void postMainThread(Runnable runnable) {
        runnable.run();
    }

    enum AlgorithmType {

        EDS(0),
        TDES(1),
        SM4(2);

        private int code;

        AlgorithmType(int code) {
            this.code = code;
        }

        public int getCode() {
            return code;
        }
    }
}
